﻿using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
using System;
using System.Net;
using System.Text;
using System.IO;

namespace Office365.SharePointOnline.Cmdlets
{
    [System.Management.Automation.Cmdlet(System.Management.Automation.VerbsData.Export, "Taxonomy")]
    public class Export_Taxonomy : System.Management.Automation.PSCmdlet
    {
        [System.Management.Automation.Parameter(Position = 0, Mandatory = true)]
        public string Url;

        [System.Management.Automation.Parameter(Position = 1, Mandatory = false)]
        public string User;

        [System.Management.Automation.Parameter(Position = 2, Mandatory = false)]
        public string Password;

        [System.Management.Automation.Parameter(Position = 3, Mandatory = false)]
        public string AuthenticationMethod;

        // use only if onpremise authentication
        [System.Management.Automation.Parameter(Position = 4, Mandatory = false)]
        public string Domain;

        // use only if onpremise authentication
        [System.Management.Automation.Parameter(Position = 5, Mandatory = true)]
        public string OutputPath;

        // Example:
        // Export-Taxonomy -Url https://foobar.sharepoint.com -User admin@foobar.onmicrosoft.com -Password pass@word1 -AuthenticationMethod SharePointOnline -Verbose -OutputPath c:\temp

        protected override void ProcessRecord()
        {
            // check parameters
            ClientContext context = new ClientContext(Url);
            ICredentials credentials;
            Web web = context.Web;
            this.WriteVerbose("Logging on.");
            if (string.IsNullOrEmpty(Url))
            {
                this.WriteObject("Error: url is not set");
            }
            else
            {
                if (!string.IsNullOrEmpty(Password) && !string.IsNullOrEmpty(User))
                {
                    AuthenticationHelper authenticationHelper = new AuthenticationHelper() { AuthenticationMethod = AuthenticationMethod, Domain = Domain, Password = Password, User = User };
                    credentials = authenticationHelper.GetCredentials();
                    if (credentials != null)
                    {
                        context.Credentials = credentials as ICredentials;
                    }
                    else
                    {
                        throw new NullReferenceException("No credentials returned.");
                    }
                }
                else
                {
                    this.WriteVerbose("Reusing existing credentials");
                }

                this.WriteVerbose("Starting to export taxonomy. This will create one or more csv files that you can import in the termstore.");
                context.Load(web);
                context.ExecuteQuery();

                // validate the outpath and quit if it does not exist
                DirectoryInfo target = new DirectoryInfo(OutputPath);
                if (target.Exists)
                {
                    // add slash at the end just in case user forgets it...
                    if (!OutputPath.EndsWith(@"\"))
                    {
                        OutputPath = OutputPath + @"\";
                    }
                    ExportTerms(context, web);
                }
                else
                {
                    throw new ArgumentException("The specified output path does not exist");
                }
            }
        }

        private void ExportTerms(ClientContext context, Web web)
        {
            // Get the taxonomy session
            this.WriteVerbose("Getting taxonomy session.");
            TaxonomySession tx = Microsoft.SharePoint.Client.Taxonomy.TaxonomySession.GetTaxonomySession(context);
            context.Load(tx);
            context.ExecuteQuery();
            
            this.WriteVerbose("loading termstores");
            var termstores = tx.TermStores;
            context.Load(termstores);
            context.ExecuteQuery();

            foreach (TermStore termstore in termstores)
            {
                this.WriteVerbose(string.Format("Loaded termstore {0}", termstore.Name));
                this.WriteVerbose("Loading groups.");
                TermGroupCollection groups = termstore.Groups;
                context.Load(groups);
                context.ExecuteQuery();
                foreach (TermGroup group in groups)
                {
                    this.WriteVerbose(group.Name);
                    TermSetCollection termsets = group.TermSets;
                    context.Load(termsets);
                    context.ExecuteQuery();
                    foreach (TermSet termset in termsets)
                    {
                        this.WriteVerbose(string.Format(">>> {0}",termset.Name));

                        // create a stringbuilder so we can hold the csv lines and then write it to the file system
                        var csv = new StringBuilder();
                        var firstline = string.Format("Term Set Name,Term Set Description,LCID,Available for Tagging,Term Description,Level 1 Term,Level 2 Term,Level 3 Term,Level 4 Term,Level 5 Term,Level 6 Term,Level 7 Term{0}", Environment.NewLine);
                        csv.Append(firstline);

                        TermCollection terms = termset.GetAllTerms();
                        context.Load(terms);
                        context.ExecuteQuery();
                        foreach (Term term in terms)
                        {
                            bool isTaggable = term.IsAvailableForTagging;
                            int lcid = termstore.WorkingLanguage; // not completely sure about this one
                            string description = term.Description;

                            string hierarchy = GetTermLevel(term, context);
                            this.WriteVerbose(string.Format(">>>>>> {0}", term.Name));

                            var newLine = string.Format("{0}{1}", TermLine(termset.Name, isTaggable, lcid, description, hierarchy), Environment.NewLine);
                            csv.Append(newLine);    
                        }

                        // write a csv file based on the termset name so we can import that somewhere else
                        this.WriteVerbose(string.Format("Exporting termset {0}.csv",termset.Name));
                        // TODO: remove static path and bring it up as input parameter
                        // validate path
                        DirectoryInfo target = new DirectoryInfo(OutputPath);
                        if (target.Exists)
                        {
                            // maybe replace with regex as this could go wrong more often due to unexpected userinput on termset name
                            string safeTermsetName = termset.Name.Replace("/", "-");
                            this.WriteVerbose(string.Format("writing to: {0}{1}.csv", OutputPath, safeTermsetName));
                            System.IO.File.WriteAllText(string.Format("{0}{1}.csv", OutputPath, safeTermsetName), csv.ToString(), Encoding.UTF8);
                        }
                    }
                }
            }
        }

        // returns the hierarchy of terms
        private string GetTermLevel(Term term, ClientContext context)
        {
            // get the appropiate level of the term
            int counter = 0;
            Term tempTerm = term;
            Term parenttempTerm = term;
            string[] termhierarchy = new string[7];
            termhierarchy[0] = ",";
            termhierarchy[1] = ",";
            termhierarchy[2] = ",";
            termhierarchy[3] = ",";
            termhierarchy[4] = ",";
            termhierarchy[5] = ",";
            termhierarchy[6] = ",";
            context.Load(tempTerm);
            context.ExecuteQuery();
            while (!parenttempTerm.IsRoot)
            {
                parenttempTerm = parenttempTerm.Parent;
                context.Load(parenttempTerm);
                context.ExecuteQuery();
                counter++;
            }
            int start = counter;
            while (start >= 0)
            {
                this.WriteVerbose(string.Format("Term: {0} level {1}", tempTerm.Name, start.ToString()));
                context.Load(tempTerm);
                context.ExecuteQuery();
                termhierarchy[start] = termhierarchy[start] + tempTerm.Name;
                if (tempTerm.IsRoot)
                {
                    break;
                }
                else
                {
                    tempTerm = tempTerm.Parent;
                    start--;
                }
            }
            StringBuilder builder = new StringBuilder();
            foreach (string value in termhierarchy)
            {
                builder.Append(value);
            }
            this.WriteVerbose(builder.ToString());
            return builder.ToString();
        }
        private string TermLine(string termsetname, bool taggable, int lcid, string description, string hierarchy)
        {
            return string.Format("{0},,{1},{2},{3}{4}",termsetname, lcid.ToString(), taggable.ToString().ToUpper(),description,hierarchy);
        }
    }
}
